﻿namespace GreenIsomer.Data.Entities.Ddl {
	using System;
	using System.IO;
	using System.Globalization;

	public class DdlWriter : IDisposable {

		private TextWriter writer;
		private bool textWritten;
		private bool spaceAdded;
		private bool startNewLine;
		private bool skipLine;
		private bool lineStarted;
		private bool newSectionStarted;
		private string newSectionName;

		public DdlWriter(TextWriter writer, ProviderSettings providerSettings) {
			if(writer == null) throw new ArgumentNullException("writer");

			this.writer = writer;
			ProviderSettings = providerSettings ?? new ProviderSettings();
		}

		public ProviderSettings ProviderSettings { get; private set; }

		public int Indent { get; set; }

		public void StartSection(string name) {
			if(textWritten) {
				skipLine = true;
				lineStarted = false;
				spaceAdded = false;
				startNewLine = true;
			}
			newSectionStarted = true;
			newSectionName = name;
		}

		public void StartNewLine() {
			if(textWritten) {
				lineStarted = false;
				spaceAdded = false;
				startNewLine = true;
			}
		}

		public void SkipLine() {
			if(textWritten) {
				lineStarted = false;
				spaceAdded = false;
				startNewLine = true;
				skipLine = true;
			}
		}

		public void AddSpace() {
			if(lineStarted) spaceAdded = true;
		}

		public void RemoveSpace() {
			spaceAdded = false;
		}

		public void WriteScript(string script) {
			WritePending();
			writer.Write(script);
		}

		private void WritePending() {

			if(spaceAdded) writer.Write(" ");

			if(startNewLine) writer.WriteLine();
			if(skipLine) writer.WriteLine();

			if(newSectionStarted) {
				if(skipLine) writer.WriteLine();
				writer.Write("/* -----");
				if(string.IsNullOrEmpty(newSectionName))
					writer.Write(new string('-', 120));
				else {
					writer.Write(" ");
					writer.Write(newSectionName);
					writer.Write(" ");
					writer.Write(new string('-', Math.Max(5, 118 - newSectionName.Length)));
				}
					
				writer.WriteLine(" */");
				writer.WriteLine();
				writer.WriteLine();
			}

			if(!lineStarted && Indent > 0) writer.Write(new string('\t', Indent));

			textWritten = true;
			lineStarted = true;
			spaceAdded = false;
			startNewLine = false;
			skipLine = false;
			newSectionStarted = false;
			newSectionName = null;
		}

		public void Dispose() {
			Dispose(true);
			GC.SuppressFinalize(this);
		}
		protected virtual void Dispose(bool disposing) {
			if(disposing && writer != null) {
				writer.Dispose();
				writer = null;
			}
		}

		public void Write(string value) {
			WritePending();

			//TODO: Consider option to treat empty as null
			if(value == null) {
				WriteNull();
				return;
			}

			string stringLiteralPrefix = ProviderSettings.StringLiteralPrefix;
			if(!string.IsNullOrEmpty(stringLiteralPrefix)) writer.Write(stringLiteralPrefix);

			writer.Write("'");

			switch(ProviderSettings.StringEscape) {
				case ProviderStringEscape.BackslashSingleQuote:
					writer.Write((value ?? string.Empty).Replace(@"\", @"\\").Replace("'", @"\'"));
					break;
				default:
					writer.Write((value ?? string.Empty).Replace("'", "''"));
					break;
			}

			writer.Write("'");
		}

		public void Write(decimal value) {
			WritePending();

			writer.Write(value.ToString(CultureInfo.InvariantCulture));
		}

		public void Write(bool value) {
			WritePending();

			switch(ProviderSettings.BooleanLiteral) {
				case ProviderBooleanLiteral.Lowercase:
					writer.Write(value ? "true" : "false");
					break;
				case ProviderBooleanLiteral.Uppercase:
					writer.Write(value ? "TRUE" : "FALSE");
					break;
				case ProviderBooleanLiteral.PascalCase:
					writer.Write(value ? "True" : "False");
					break;
				default:
					writer.Write(value ? "1" : "0");
					break;
			}
		}

		public void WriteName(string name) {
			if(name == null) return;

			WritePending();
			writer.Write("\"");
			writer.Write(name.Replace(@"""", @""""""));
			writer.Write("\"");
		}

		public void WriteSchemaPrefix(string schema) {
			if(string.IsNullOrEmpty(schema)) return;
			WriteName(schema);
			writer.Write(".");
		}


		public void WriteNull() {
			WritePending();
			writer.Write("NULL");
		}

		public void Write(Guid value) {
			WritePending();
			switch(ProviderSettings.GuidFormat) {
				case ProviderGuidFormat.StringWithBraces:
					writer.Write("{'");
					writer.Write(value.ToString());
					writer.Write("'}");
					break;
				case ProviderGuidFormat.Binary:
					writer.Write("'");
					writer.Write(BitConverter.ToString(value.ToByteArray()).Replace("-", string.Empty));
					writer.Write("'");
					break;
				case ProviderGuidFormat.FunctionWithoutBraces:
					WriteGuidFunction(value.ToString());
					break;
				case ProviderGuidFormat.FunctionWithBraces:
					WriteGuidFunction("{" + value.ToString() + "}");
					break;
				default:
					writer.Write("'");
					writer.Write(value.ToString());
					writer.Write("'");
					break;
			}
		}

		private void WriteGuidFunction(string guid) {
			FunctionRef function = ProviderSettings.GuidFunction;
			if(function == null || string.IsNullOrEmpty(function.Name)) {
				writer.Write("setguid");
			} else {
				WriteSchemaPrefix(function.Schema);
				WriteName(function.Name);
			}
			writer.Write("('");
			writer.Write(guid);
			writer.Write("')");
		}

		public void Write(DateTime value) {
			WritePending();
			if(string.IsNullOrEmpty(ProviderSettings.DateTimeFormat))
				writer.Write(value.ToString(CultureInfo.InvariantCulture));
			else {
				string formattedDate;
				try {
					formattedDate = value.ToString(ProviderSettings.DateTimeFormat, CultureInfo.InvariantCulture);
				} catch(FormatException) {
					WriteNull();
					return;
				}
				writer.Write("'");
				writer.Write(formattedDate);
				writer.Write("'");
			}
		}

	}

}